//
//  JXMovableCellTableView.swift
//  MoyoungDevice
//
//  Created by 易先生 on 2022/8/30.
//

import UIKit

@objc protocol JXMovableCellTableViewDataSource: UITableViewDataSource {
	@objc func dataSourceArrayInTableView(_ tableView: JXMovableCellTableView) -> [Any]
}

@objc protocol JXMovableCellTableViewDelegate: UITableViewDelegate {
	@objc optional func tableView(_ tableView: JXMovableCellTableView, willMoveCellAtIndexPath indexPath: IndexPath)
	@objc optional func tableView(_ tableView: JXMovableCellTableView, didMoveCellFromIndexPath fromIndexPath: IndexPath, toIndexPath: IndexPath)
	@objc optional func tableView(_ tableView: JXMovableCellTableView, endMoveCellAtIndexPath indexPath: IndexPath)
	@objc optional func tableView(_ tableView: JXMovableCellTableView, tryMoveUnmovableCellAtIndexPath indexPath: IndexPath)
	@objc optional func tableView(_ tableView: JXMovableCellTableView, customizeMovalbeCell movableCellsnapshot: UIImageView)
	@objc optional func tableView(_ tableView: JXMovableCellTableView, customizeStartMovingAnimation movableCellsnapshot: UIImageView, fingerPoint: CGPoint)

}

let kJXMovableCellAnimationTime: TimeInterval = 0.25

class JXMovableCellTableView: UITableView {

	override init(frame: CGRect, style: UITableView.Style) {
		super.init(frame: frame, style: style)
		self.jx_initData()
		self.jx_addGesture()
	}

	required init?(coder: NSCoder) {
		fatalError("init(coder:) has not been implemented")
	}

	var longPressGesture: UILongPressGestureRecognizer!
	var gestureMinimumPressDuration: TimeInterval!
	var selectedIndexPath: IndexPath!
	var snapshot: UIImageView!
	var tempDataSource: [Any]!
	var edgeScrollTimer: CADisplayLink!
	var canEdgeScroll: Bool!
	var edgeScrollRange: CGFloat!
	weak var jxDataSource: JXMovableCellTableViewDataSource!
    weak var jxDelegate: JXMovableCellTableViewDelegate!
	var fromIndexPath: IndexPath!
	var toIndexPath: IndexPath!

	func jx_initData() {
		gestureMinimumPressDuration = 0.5
		canEdgeScroll = true
		edgeScrollRange = 150.0
	}

	func jx_addGesture() {
		longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(jx_processGesture(gesture:)))
		longPressGesture.minimumPressDuration = gestureMinimumPressDuration
		self.addGestureRecognizer(longPressGesture)
	}

	@objc func jx_processGesture(gesture: UILongPressGestureRecognizer) {

		switch gesture.state {
		case .began:
			self.jx_gestureBegan(gesture: gesture)
		case .changed:
			if !canEdgeScroll {
				self.jx_gestureChanged(gesture: gesture)
			}
		case .ended:
			self.jx_gestureEndedOrCancelled(gesture: gesture)
		case .cancelled:
			self.jx_gestureEndedOrCancelled(gesture: gesture)
		default:
			break
		}
	}

	func jx_gestureBegan(gesture: UILongPressGestureRecognizer) {
		let point: CGPoint = gesture.location(in: gesture.view)
		guard let selectedIndexPath = self.indexPathForRow(at: point) else {
			return
		}
		guard let cell: UITableViewCell = self.cellForRow(at: selectedIndexPath) else {
			return
		}
		if let dateSource = self.jxDataSource,
			dateSource.responds(to: #selector(self.dataSource?.tableView(_: canMoveRowAt:))) {

			if dateSource.tableView?(self, canMoveRowAt: selectedIndexPath) == nil {
				let shakeAnimation = CAKeyframeAnimation(keyPath: "transform.translation.x")
				shakeAnimation.duration = 0.25
				shakeAnimation.values = [-20, 20, -10, 10, 0]
				cell.layer.add(shakeAnimation, forKey: "shake")

				if let delegate = self.jxDelegate, delegate.responds(to: #selector(delegate.tableView(_: tryMoveUnmovableCellAtIndexPath:))) {
					delegate.tableView?(self, tryMoveUnmovableCellAtIndexPath: selectedIndexPath)
				}
				return
			}
		}

		self.fromIndexPath = selectedIndexPath
		if let delegate = self.jxDelegate, delegate.responds(to: #selector(delegate.tableView(_: willMoveCellAtIndexPath:))) {
			delegate.tableView?(self, willMoveCellAtIndexPath: selectedIndexPath)
		}

		if canEdgeScroll {
			self.jx_startEdgeScroll()
		}

		if let dataSource = self.jxDataSource,
			dataSource.responds(to: #selector(dataSource.dataSourceArrayInTableView(_:))) {
			tempDataSource = dataSource.dataSourceArrayInTableView(self)
		}

		self.selectedIndexPath = selectedIndexPath

		self.snapshot = self.jx_snapshotViewWithInputView(inputView: cell)
		self.snapshot.alpha = 0.8

		if let delegate = self.jxDelegate, delegate.responds(to: #selector(delegate.tableView(_: customizeMovalbeCell:))) {
			delegate.tableView?(self, customizeMovalbeCell: snapshot)
		} else {
			snapshot.layer.shadowColor = UIColor.gray.cgColor
			snapshot.layer.masksToBounds = false
			snapshot.layer.cornerRadius = 0
			snapshot.layer.shadowOffset = CGSize(width: -5, height: 0)
			snapshot.layer.shadowOpacity = 0.4
			snapshot.layer.shadowRadius = 5
		}

		snapshot.frame = cell.frame
		self.addSubview(snapshot)
		cell.isHidden = true
		if let delegate = self.jxDelegate, delegate.responds(to: #selector(delegate.tableView(_: customizeStartMovingAnimation: fingerPoint:))) {
			delegate.tableView?(self, customizeStartMovingAnimation: snapshot, fingerPoint: point)
		} else {
			UIView.animate(withDuration: kJXMovableCellAnimationTime) { [weak self] in
				if self != nil {
					self!.snapshot.center = CGPoint(x: self!.snapshot.center.x, y: point.y)
				}
			}
		}

	}

	func jx_gestureChanged(gesture: UILongPressGestureRecognizer) {

		let point: CGPoint = gesture.location(in: gesture.view)
		snapshot.center = CGPoint(x: snapshot.center.x, y: self.snapshotYToFitTargetY(targetY: point.y))

		guard let currentIndexPath = self.indexPathForRow(at: point) else {
			return
		}

		guard let selectedCell: UITableViewCell = self.cellForRow(at: selectedIndexPath) else {
			return
		}
		selectedCell.isHidden = true
		if let dateSource = self.jxDataSource, dateSource.responds(to: #selector(self.dataSource?.tableView(_: canMoveRowAt:))) {
			if dateSource.tableView?(self, canMoveRowAt: currentIndexPath) == nil {

				return
			}
		}

		if selectedIndexPath != currentIndexPath {
			self.jx_updateDataSourceAndCellFromIndexPath(fromIndexPath: selectedIndexPath, toIndexPath: currentIndexPath)
		}

		if let delegate = self.jxDelegate, delegate.responds(to: #selector(delegate.tableView(_: didMoveCellFromIndexPath: toIndexPath:))) {
			delegate.tableView?(self, didMoveCellFromIndexPath: selectedIndexPath, toIndexPath: currentIndexPath)
		}

		selectedIndexPath = currentIndexPath

	}

	func jx_gestureEndedOrCancelled(gesture: UILongPressGestureRecognizer) {
		if canEdgeScroll {
			self.jx_stopEdgeScroll()
		}

		toIndexPath = selectedIndexPath
		if let delegate = self.jxDelegate, delegate.responds(to: #selector(delegate.tableView(_: endMoveCellAtIndexPath:))) {
			delegate.tableView?(self, endMoveCellAtIndexPath: selectedIndexPath)
		}

		if let dataSource = self.jxDataSource, dataSource.responds(to: #selector(dataSource.tableView(_: moveRowAt: to:))) {
			if fromIndexPath != nil && toIndexPath != nil && fromIndexPath != toIndexPath {
				dataSource.tableView?(self, moveRowAt: fromIndexPath, to: toIndexPath)
			}

		}
		guard let cell = self.cellForRow(at: selectedIndexPath) else {
			return
		}

		UIView.animate(withDuration: kJXMovableCellAnimationTime, animations: { [weak self] in
			if self != nil && self!.snapshot != nil {
				self!.snapshot.transform = CGAffineTransform.identity
				self!.snapshot.frame = cell.frame
			}

		}) { [weak self] (_) in
			cell.isHidden = false
			if self != nil && self!.snapshot != nil {
				self!.snapshot.removeFromSuperview()
				self!.snapshot = nil
			}

		}
	}

	func jx_snapshotViewWithInputView(inputView: UIView) -> UIImageView {
		return UIImageView(image: inputView.screenShot())
	}

	func jx_updateDataSourceAndCellFromIndexPath(fromIndexPath: IndexPath, toIndexPath: IndexPath) {
		if self.numberOfSections == 1 {
			if tempDataSource.count > fromIndexPath.row && tempDataSource.count > toIndexPath.row {
				let from = tempDataSource[fromIndexPath.row]
				let to = tempDataSource[toIndexPath.row]
				tempDataSource[fromIndexPath.row] = to
				tempDataSource[toIndexPath.row] = from
			}
			self.moveRow(at: fromIndexPath, to: toIndexPath)

		} else {
//            id fromData = _tempDataSource[fromIndexPath.section][fromIndexPath.row];
//            id toData = _tempDataSource[toIndexPath.section][toIndexPath.row];
//            NSMutableArray *fromArray = _tempDataSource[fromIndexPath.section];
//            NSMutableArray *toArray = _tempDataSource[toIndexPath.section];
//            [fromArray replaceObjectAtIndex:fromIndexPath.row withObject:toData];
//            [toArray replaceObjectAtIndex:toIndexPath.row withObject:fromData];
//            [_tempDataSource replaceObjectAtIndex:toIndexPath.section withObject:toArray];
//
//            [self beginUpdates];
//            [self moveRowAtIndexPath:fromIndexPath toIndexPath:toIndexPath];
//            [self moveRowAtIndexPath:toIndexPath toIndexPath:fromIndexPath];
//            [self endUpdates];
		}
	}

	func snapshotYToFitTargetY(targetY: CGFloat) -> CGFloat {
		let minValue = snapshot.bounds.size.height / 2.0
		let maxValue = self.contentSize.height - minValue
		return min(maxValue, max(minValue, targetY))
	}

	func jx_startEdgeScroll() {
		edgeScrollTimer = CADisplayLink(target: self, selector: #selector(jx_processEdgeScroll))
		edgeScrollTimer.add(to: .main, forMode: .common)
	}

	@objc func jx_processEdgeScroll() {
		self.jx_gestureChanged(gesture: longPressGesture)
		let minOffsetY = self.contentOffset.y + edgeScrollRange
		let maxOffsetY = self.contentOffset.y + self.bounds.size.height - edgeScrollRange
		let touchPoint = snapshot.center

		if touchPoint.y < edgeScrollRange {
			if self.contentOffset.y <= 0 {
				return
			} else {
				if self.contentOffset.y - 1 < 0 {
					return
				}
				self.setContentOffset(CGPoint(x: self.contentOffset.x, y: self.contentOffset.y - 1), animated: false)
				snapshot.center = CGPoint(x: snapshot.center.x, y: self.snapshotYToFitTargetY(targetY: snapshot.center.y - 1))
			}
		}

		if touchPoint.y > self.contentSize.height - edgeScrollRange {
			if self.contentOffset.y >= self.contentSize.height - self.bounds.size.height {
				return
			} else {
				if self.contentOffset.y + 1 > self.contentSize.height - self.bounds.size.height {
					return
				}
				self.setContentOffset(CGPoint(x: self.contentOffset.x, y: self.contentOffset.y + 1), animated: false)
				snapshot.center = CGPoint(x: snapshot.center.x, y: self.snapshotYToFitTargetY(targetY: snapshot.center.y + 1))
			}
		}

		let maxMoveDistance: CGFloat = 20
		if touchPoint.y < minOffsetY {
			let moveDistance = (minOffsetY - touchPoint.y) / edgeScrollRange * maxMoveDistance
			self.setContentOffset(CGPoint(x: self.contentOffset.x, y: self.contentOffset.y - moveDistance), animated: false)
			snapshot.center = CGPoint(x: snapshot.center.x, y: self.snapshotYToFitTargetY(targetY: snapshot.center.y - moveDistance))
		} else if touchPoint.y > maxOffsetY {
			let moveDistance = (touchPoint.y - maxOffsetY) / edgeScrollRange * maxMoveDistance
			self.setContentOffset(CGPoint(x: self.contentOffset.x, y: self.contentOffset.y + moveDistance), animated: false)
			snapshot.center = CGPoint(x: snapshot.center.x, y: self.snapshotYToFitTargetY(targetY: snapshot.center.y + moveDistance))
		}
	}

	func jx_stopEdgeScroll() {
		if edgeScrollTimer != nil {
			edgeScrollTimer.invalidate()
			edgeScrollTimer = nil
		}
	}
}
